#include <cassert>
#include <string>
#include <iostream>
#include <memory>
#include <algorithm>

#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

#ifdef _WIN32
#pragma warning(push, 3) // prevent warnings from json headers
#endif
#include <json_spirit_reader_template.h>
#include <json_spirit_writer_template.h>
#ifdef _WIN32
#pragma warning(pop)
#endif

#include "Vector2.h"
#include "GameInfo.h"
#include "Commands.h"
#include "Handshaking.h"
#include "json.h"
#include "Commander.h"
#include "CommanderFactory.h"
#include "boost/thread/detail/thread.hpp"

using namespace std;

using namespace boost::asio;
using namespace boost::asio::ip;

class NetworkCommanderClient
{
public:
    NetworkCommanderClient(tcp::socket& socket, const string& commanderName)
    :   m_socket(socket)
    ,   m_commander(CommanderFactory::getInstance().create(commanderName))
    ,   m_bufferMem()
    {
        m_bufferMem.reserve(1024 * 1024);
    }

    void performHandshaking()
    {
        string message = readLine();
        if (message != "<connect>")
        {
            cerr << "Expected connect message from the game server. Received: " << message << endl;
            assert(false);
            exit(1);
        }

        json_spirit::mValue value;
        string connectServerJson = readLine();
        json_spirit::read_string(connectServerJson, value);
        auto connectServer = fromJSON<ConnectServer>(value);
        if (!connectServer->validate())
        {
            assert(false);
            exit(1);
        }

        ConnectClient connectClient;
        connectClient.m_commanderName = m_commander->getName();
        connectClient.m_language = "C++";
        string connectClientJson = toJSON(connectClient);
        string connectMessage = "<connect>\n" + connectClientJson + "\n";
        boost::system::error_code ec;
        boost::asio::write(m_socket, buffer(connectMessage), ec);
        if (ec)
        {
            cerr << "Error sending data to server: " << ec.message() << endl;
            assert(false);
            exit(1);
        }
    }

    void run()
    {
        performHandshaking();

        bool initialized = false;
        bool tickRequired = false;

        for (;;)
        {
            bool moreData = true;
            while (moreData)
            {
                string message = readLineNonBlocking();
                if (message == "")
                {
                    moreData = false;
                }
                else if (message == "<initialize>")
                {
                    assert(!initialized);
                    string levelInfoJson = readLine();
                    string gameInfoJson = readLine();
                    initializeCommanderGameData(levelInfoJson, gameInfoJson);
                    m_commander->initialize();
                    sendReady();
                    initialized = true;
                }
                else if (message == "<tick>")
                {
                    assert(initialized);
                    string gameInfoJson = readLine();
                    updateCommanderGameData(gameInfoJson);
                    tickRequired = true;
                }
                else if (message == "<shutdown>")
                {
                    assert(initialized);
                    m_commander->shutdown();
                    return;
                }
                else
                {
                    cerr <<  "Received unexpected message from server: " << message << endl;
                    assert(false);
                    exit(1);
                }
            }

            if (tickRequired)
            {
                m_commander->tick();
                sendCommands();
                tickRequired = false;
            }
          
            boost::this_thread::sleep(boost::posix_time::microseconds(100));
        }
    }

private:

    void readAllDataFromSocket()
    {
        const size_t maxTempDataSize = 16 * 1024;
        unsigned char tempData[maxTempDataSize];
        m_socket.non_blocking(true);
        for (;;)
        {
            boost::system::error_code ec;
            size_t numBytes = read(m_socket, buffer(tempData, maxTempDataSize), ec);
            if (numBytes > 0)
            {
                m_bufferMem.insert(m_bufferMem.end(), tempData, tempData + numBytes);
            }
            if ((ec == error::would_block) || (ec == error::try_again) || (ec == error::eof))
            {
                break;
            }
            else if (ec)
            {
                cerr << "Error receiving data from server: " << ec.message() << endl;
                assert(false);
                exit(1);
            }
        }
        m_socket.non_blocking(false);
    }
    
    string readLineNonBlocking()
    {
        readAllDataFromSocket();
        vector<unsigned char>::iterator newlineIndex = find(m_bufferMem.begin(), m_bufferMem.end(), '\n');
        if (newlineIndex == m_bufferMem.end())
        {
            return "";
        }
        string line(m_bufferMem.begin(), newlineIndex);
        newlineIndex++;
        m_bufferMem.erase(m_bufferMem.begin(), newlineIndex);     
        return line;
    }

    string readLine()
    {
        string line;
        do 
        {
            line = readLineNonBlocking();
        } while (line == "");
        return line;
    }

    void initializeCommanderGameData(const string&  levelInfoJson, const string&  gameInfoJson)
    {
        json_spirit::mValue value;

        json_spirit::read_string(levelInfoJson, value);
        m_commander->m_level = fromJSON<LevelInfo>(value);

        json_spirit::read_string(gameInfoJson, value);
        m_commander->m_game = fromJSON<GameInfo>(value);
        fixupGameInfoReferences(*m_commander->m_game);
    }

    void updateCommanderGameData(const string& gameInfoJson)
    {
        json_spirit::mValue value;
        json_spirit::read_string(gameInfoJson, value);
        unique_ptr<GameInfo> game = fromJSON<GameInfo>(value);
        mergeGameInfo(*m_commander->m_game, *game);

        m_commander->m_game->bots_alive.clear();
        m_commander->m_game->bots_available.clear();
        m_commander->m_game->enemyFlags.clear();

        for(map<string, unique_ptr<FlagInfo> >::iterator i=m_commander->m_game->flags.begin(), end=m_commander->m_game->flags.end(); i!=end; ++i)
        {
            FlagInfo* flag = i->second.get();
            if(flag->team == m_commander->m_game->team)
                continue;

            m_commander->m_game->enemyFlags.push_back(flag);
        }

        for(vector<BotInfo*>::iterator i=m_commander->m_game->team->members.begin(), end=m_commander->m_game->team->members.end(); i!=end; ++i)
        {
            BotInfo* bot = *i;

            if(!bot || bot->health <= 0.0f)
                continue;

            m_commander->m_game->bots_alive.push_back(bot);

            if(*bot->state == BotInfo::STATE_IDLE)
                m_commander->m_game->bots_available.push_back(bot);
        }
    }

    void sendReady()
    {
        string message = "<ready>\n";
        boost::system::error_code ec;
        boost::asio::write(m_socket, buffer(message), ec);
        if (ec)
        {
            cerr << "Error sending <ready> data to server: " << ec.message() << endl;
            assert(false);
            exit(1);
        }
    }

    void sendCommands()
    {
        for (auto command = m_commander->m_commands.begin(); command != m_commander->m_commands.end(); ++command)
        {
            string commandJson = toJSON(**command);
            string message = "<command>\n" + commandJson + "\n";
            boost::system::error_code ec;
            boost::asio::write(m_socket, buffer(message), ec);
            if (ec)
            {
                cerr << "Error sending <command> data to server: " << ec.message() << endl;
                assert(false);
                exit(1);
            }
        }
        m_commander->m_commands.clear();
    }

    tcp::socket& m_socket;
    unique_ptr<Commander> m_commander;
    vector<unsigned char> m_bufferMem;
};


int main(int argc, char** argv)
{
    string HOST = "localhost";
    string PORT = "41041";
    string commanderName;

    CommanderFactory& commanderFactory = CommanderFactory::getInstance();

    if ((argc == 1) && (commanderFactory.getCommanderCount() == 1))
    {
        commanderName = commanderFactory.getCommanderNames()[0];
    }
    else if (argc == 2)
    {
        commanderName = argv[1];
    }
    else if ((argc == 3) && (commanderFactory.getCommanderCount() == 1))
    {
        HOST = argv[1];
        PORT = argv[2];
        commanderName = commanderFactory.getCommanderNames()[0];
    }
    else if (argc == 4)
    {
        HOST = argv[1];
        PORT = argv[2];
        commanderName = argv[3];
    }
    else
    {
        cerr << "Usage: client [<hostname> <port>] [<commander_name>]" << endl;
        cerr << "eg. client localhost 41041 MyCommander" << endl;
        return 1;
    }

    if (!CommanderFactory::getInstance().exists(commanderName))
    {
        cerr << "Unable to create commander '" << commanderName << "'." << endl;
        return 1;
    }

    io_service io_service;
    tcp::socket socket(io_service);

    cout << "Connecting to " << HOST << ":" << PORT << endl;
    bool connected = false;
    for (int i = 0; i < 1000; ++i)
    {
        tcp::resolver resolver(io_service);
        tcp::resolver::query query(HOST, PORT);
        tcp::resolver::iterator iterator = resolver.resolve(query);
        boost::system::error_code ec;
        connect(socket, iterator, ec);
        if (!ec)
        {
            cout << "Connected!" << endl;
            connected = true;
            break;
        }
        boost::this_thread::sleep(boost::posix_time::milliseconds(10));
    }

    if (!connected)
    {
        cerr << "Unable to connect to " << HOST << ":" << PORT << endl;
        return 1;
    }

    NetworkCommanderClient wrapper(socket, commanderName);
    wrapper.run();

    return 0;
}
